/*
 * Copyright 2000-2015 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.intellij.psi.impl.source.tree;

import com.intellij.lang.ASTNode;
import com.intellij.lang.Language;
import com.intellij.navigation.ItemPresentation;
import com.intellij.navigation.NavigationItem;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.impl.CheckUtil;
import com.intellij.psi.impl.ResolveScopeManager;
import com.intellij.psi.impl.SharedPsiElementImplUtil;
import com.intellij.psi.impl.source.SourceTreeToPsiMap;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;

public abstract class CompositePsiElement extends CompositeElement implements PsiElement, NavigationItem {
    private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.tree.CompositePsiElement");

    protected static int ourHC = 0;

    protected CompositePsiElement(IElementType type) {
        super(type);
        setPsi(this);
    }

    @NotNull
    @Override
    public CompositePsiElement clone() {
        CompositePsiElement clone = (CompositePsiElement) super.clone();
        clone.setPsi(clone);
        return clone;
    }

    @Override
    @NotNull
    public PsiElement[] getChildren() {
        return getChildrenAsPsiElements((TokenSet) null, PsiElement.ARRAY_FACTORY);
    }

    @Override
    public PsiElement getFirstChild() {
        ASTNode node = getFirstChildNode();
        return node != null ? node.getPsi() : null;
    }

    @Override
    public PsiElement getLastChild() {
        ASTNode node = getLastChildNode();
        return node != null ? node.getPsi() : null;
    }

    @Override
    public void acceptChildren(@NotNull PsiElementVisitor visitor) {
        PsiElement child = getFirstChild();
        while (child != null) {
            child.accept(visitor);
            child = child.getNextSibling();
        }
    }

    @Override
    public PsiElement getParent() {
        final CompositeElement parentNode = getTreeParent();
        return parentNode != null ? parentNode.getPsi() : null;
    }

    @Override
    public PsiElement getNextSibling() {
        ASTNode node = getTreeNext();
        return node != null ? node.getPsi() : null;
    }

    @Override
    public PsiElement getPrevSibling() {
        ASTNode node = getTreePrev();
        return node != null ? node.getPsi() : null;
    }

    @Override
    public PsiFile getContainingFile() {
        PsiFile file = SharedImplUtil.getContainingFile(this);
        if (file == null) throw new PsiInvalidElementAccessException(this);
        return file;
    }

    @Override
    public PsiElement findElementAt(int offset) {
        ASTNode leaf = findLeafElementAt(offset);
        return SourceTreeToPsiMap.treeElementToPsi(leaf);
    }

    @Override
    public PsiReference findReferenceAt(int offset) {
        return SharedPsiElementImplUtil.findReferenceAt(this, offset);
    }

    @Override
    public PsiElement copy() {
        /*ASTNode elementCopy = copyElement();
        return SourceTreeToPsiMap.treeElementToPsi(elementCopy);*/
        throw new Error();
    }

    @Override
    public boolean isValid() {
        return SharedImplUtil.isValid(this);
    }

    @Override
    public boolean isWritable() {
        return SharedImplUtil.isWritable(this);
    }

    @Override
    public PsiReference getReference() {
        return null;
    }

    @Override
    @NotNull
    public PsiReference[] getReferences() {
        return SharedPsiElementImplUtil.getReferences(this);
    }

    @Override
    public PsiElement add(@NotNull PsiElement element) throws IncorrectOperationException {
        return addInnerBefore(element, null);
    }

    @Override
    public PsiElement addBefore(@NotNull PsiElement element, PsiElement anchor) throws IncorrectOperationException {
        return addInnerBefore(element, anchor);
    }

    @Override
    public PsiElement addAfter(@NotNull PsiElement element, PsiElement anchor) throws IncorrectOperationException {
        /*CheckUtil.checkWritable(this);
        TreeElement elementCopy = ChangeUtil.copyToElement(element);
        TreeElement treeElement = addInternal(elementCopy, elementCopy, SourceTreeToPsiMap.psiElementToTree(anchor), Boolean.FALSE);
        return ChangeUtil.decodeInformation(treeElement).getPsi();*/
        throw new Error();
    }

    @Override
    public final void checkAdd(@NotNull PsiElement element) throws IncorrectOperationException {
        CheckUtil.checkWritable(this);
    }

    @Override
    public final PsiElement addRange(PsiElement first, PsiElement last) throws IncorrectOperationException {
        return SharedImplUtil.addRange(this, first, last, null, null);
    }

    @Override
    public final PsiElement addRangeBefore(@NotNull PsiElement first, @NotNull PsiElement last, PsiElement anchor) throws IncorrectOperationException {
        return SharedImplUtil.addRange(this, first, last, SourceTreeToPsiMap.psiElementToTree(anchor), Boolean.TRUE);
    }

    @Override
    public final PsiElement addRangeAfter(PsiElement first, PsiElement last, PsiElement anchor) throws IncorrectOperationException {
        return SharedImplUtil.addRange(this, first, last, SourceTreeToPsiMap.psiElementToTree(anchor), Boolean.FALSE);
    }

    @Override
    public void delete() throws IncorrectOperationException {
        /*LOG.assertTrue(getTreeParent() != null, "Parent not found for " + this);
        CheckUtil.checkWritable(this);
        getTreeParent().deleteChildInternal(this);
        invalidate();*/
        throw new Error();
    }

    @Override
    public void checkDelete() throws IncorrectOperationException {
        CheckUtil.checkWritable(this);
    }

    @Override
    public void deleteChildRange(PsiElement first, PsiElement last) throws IncorrectOperationException {
        CheckUtil.checkWritable(this);
        ASTNode firstElement = first.getNode();
        ASTNode lastElement = last.getNode();
        LOG.assertTrue(firstElement.getTreeParent() == this);
        LOG.assertTrue(lastElement.getTreeParent() == this);
        //CodeEditUtil.removeChildren(this, firstElement, lastElement);
    }

    @Override
    public PsiElement replace(@NotNull PsiElement newElement) throws IncorrectOperationException {
        return SharedImplUtil.doReplace(this, this, newElement);
    }

    @Override
    public void accept(@NotNull PsiElementVisitor visitor) { //TODO: remove this method!!
        visitor.visitElement(this);
    }

    /*@Override
    public boolean processDeclarations(@NotNull PsiScopeProcessor processor,
                                       @NotNull ResolveState state,
                                       PsiElement lastParent,
                                       @NotNull PsiElement place) {
        return true;
    }*/

    public String toString() {
        return "PsiElement" + "(" + getElementType().toString() + ")";
    }

    @Override
    public PsiElement getContext() {
        return getParent();
    }

    @Override
    @NotNull
    public PsiElement getNavigationElement() {
        return this;
    }

    @Override
    public PsiElement getOriginalElement() {
        return this;
    }

    @Override
    public boolean isPhysical() {
        PsiFile file = getContainingFile();
        return file != null && file.isPhysical();
    }

    @Override
    @NotNull
    public GlobalSearchScope getResolveScope() {
        return ResolveScopeManager.getElementResolveScope(this);
    }

    @Override
    @NotNull
    public SearchScope getUseScope() {
        return ResolveScopeManager.getElementUseScope(this);
    }

    @Override
    public ItemPresentation getPresentation() {
        return null;
    }

    @Override
    public String getName() {
        return null;
    }

    /*@Override
    public void navigate(boolean requestFocus) {
        Navigatable descriptor = PsiNavigationSupport.getInstance().getDescriptor(this);
        if (descriptor != null) descriptor.navigate(requestFocus);
    }

    @Override
    public boolean canNavigate() {
        return PsiNavigationSupport.getInstance().canNavigate(this);
    }

    @Override
    public boolean canNavigateToSource() {
        return canNavigate();
    }*/

    @Override
    @NotNull
    public Project getProject() {
        /*Project project = ProjectCoreUtil.theOnlyOpenProject();
        if (project != null) {
            return project;
        }*/
        final PsiManager manager = getManager();
        if (manager == null) throw new PsiInvalidElementAccessException(this);

        return manager.getProject();
    }

    @Override
    @NotNull
    public Language getLanguage() {
        return getElementType().getLanguage();
    }

    @Override
    @NotNull
    public ASTNode getNode() {
        return this;
    }

    private PsiElement addInnerBefore(final PsiElement element, final PsiElement anchor) throws IncorrectOperationException {
        /*CheckUtil.checkWritable(this);
        TreeElement elementCopy = ChangeUtil.copyToElement(element);
        TreeElement treeElement = addInternal(elementCopy, elementCopy, SourceTreeToPsiMap.psiElementToTree(anchor), Boolean.TRUE);
        if (treeElement != null) return ChangeUtil.decodeInformation(treeElement).getPsi();
        throw new IncorrectOperationException("Element cannot be added");*/
        throw new Error();
    }

    @Override
    public PsiManager getManager() {
        throw new Error();
    }

    @Override
    public boolean isEquivalentTo(final PsiElement another) {
        return this == another;
    }
}

